package correctness;

import static org.junit.Assert.*;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.Output;
import model.simulationmodel.LogicValue;
import model.simulationmodel.SimulationModel;
import model.simulationmodel.logicelements.LogicSwitch;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import controller.commoncontroller.UtilityDialogs;

import view.UtilityConstants;

/**
 * <b> Mit dieser Testklasse wird Folgendes getestet:</b> <br>
 * <br>
 * Ein RS-FlipFlop wird nachgebaut. Dies geschieht mit Hilfe von zwei Schaltern,
 * zwei NOT-Gattern, zwei NAND-Gattern und zwei Lampen. Anschliessend wird der
 * FlipFlop-Nachbau als selbstdefiniertes Bauteil gespeichert. Danach wird eine
 * neue Schaltung erstellt, der FlipFlop-Nachbau hinzugefuegt und die Simulation
 * mit jeder moeglichen logischen Belegung getestet.
 * 
 * @author Team Locsi
 * 
 */
public class FlipFlopReproductionTest {

	private static EditorModel eModel;
	private static SimulationModel sModel;

	/**
	 * Diese Methode wird vor allen anderen aufgerufen. Hier werden das
	 * <code>EditorModel</code>-Objekt und das <code>SimulationModel</code>
	 * -Objekt initialisiert. Zugleich wird ein FlipFlop-Nachbau erstellt.
	 * Anschliessend wird das neu erzeugte selbstdefinierte Bauteil zu einer
	 * neuen Schaltung hinzugefuegt. Ausserdem werden zwei Schalter und zwei
	 * Lampen hinzugefuegt und mit dem Bauteil verbunden.
	 */
	@BeforeClass
	public static void initialize() {

		// Initialisieren einer neuen Schaltung
		eModel = new EditorModel();

		// Ein FlipFlop-Nachbau wird erstellt und gespeichert
		createFlipFlopReproduction();

		// Eine neue Schaltung wird erstellt
		eModel.newCircuit();

		// Zur Schaltung werden folgende Bauteile hinzugefuegt: zwei Schalter,
		// zwei Lampen und das selbstdefinierte Bauteil, das zuvor erstellt
		// wurde
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH", 10, 10);
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH", 10, 60);
		eModel.addLogicElement(LogicType.SELFDEFINED, "JUNIT_FLIPFLOP", 155, 35);
		eModel.addLogicElement(LogicType.LAMP, "LAMP", 300, 10);
		eModel.addLogicElement(LogicType.LAMP, "LAMP", 300, 60);

		// Um Verbindungen setzen zu koennen, werden die Inputs und Outputs der
		// einzelnen Bauteile benoetigt. Dazu werden hier die benoetigten Inputs
		// der einzelnen Bauteile gespeichert.
		Map<String, Input> selfdefined3Inputs = new HashMap<String, Input>(
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("JUNIT_FLIPFLOP_3").getInputs());
		Map<String, Input> lamp4Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("LAMP_4").getInputs());
		Map<String, Input> lamp5Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("LAMP_5").getInputs());

		// Hier werden die benoetigten Outputs der einzelnen Bauteile
		// gespeichert
		Map<String, Output> switch1Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("SWITCH_1").getOutputs());
		Map<String, Output> switch2Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("SWITCH_2").getOutputs());
		Map<String, Output> selfdefined3Outputs = new HashMap<String, Output>(
				eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get("JUNIT_FLIPFLOP_3").getOutputs());

		// Der Ausgang von SWITCH_1 wird mit dem ersten Eingang von
		// JUNIT_FLIPFLOP_3 verbunden.
		eModel.setConnection(switch1Outputs.get(Output.STD_O),
				selfdefined3Inputs.get("SWITCH_SELF_1"));

		// Der Ausgang von SWITCH_2 wird mit dem zweiten Eingang von
		// JUNIT_FLIPFLOP_3 verbunden.
		eModel.setConnection(switch2Outputs.get(Output.STD_O),
				selfdefined3Inputs.get("SWITCH_SELF_2"));

		// Der erste Ausgang von JUNIT_FLIPFLOP_3 wird mit dem Eingang von
		// LAMP_4 verbunden.
		eModel.setConnection(selfdefined3Outputs.get("LAMP_SELF_7"),
				lamp4Inputs.get(Input.STD_A));

		// Der zweite Ausgang von JUNIT_FLIPFLOP_3 wird mit dem Eingang von
		// LAMP_5 verbunden.
		eModel.setConnection(selfdefined3Outputs.get("LAMP_SELF_8"),
				lamp5Inputs.get(Input.STD_A));

		// Das SimulationModel-Objekt wird initialisiert
		sModel = new SimulationModel(eModel);
		sModel.updateSimulationElements();
	}

	/**
	 * Diese Methode wird vor jeder Test-Methode aufgerufen. Hier werden alle
	 * bisherigen Aenderungen zurueckgesetzt.
	 */
	@Before
	public void reset() {
		initialize();
	}

	/**
	 * In dieser privaten Hilfsmethode wird ein FlipFlop-Nachbau erstellt und
	 * als selbstdefiniertes Bauteil gespeichert.
	 */
	private static void createFlipFlopReproduction() {

		// Erstellen eines FlipFlop-Nachbaus. Dazu werden zwei Schalter, zwei
		// NOT-Gatter, zwei NAND-Gatter und zwei Lampen hinzugefuegt
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH_SELF", 10, 10);
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH_SELF", 10, 600);
		eModel.addLogicElement(LogicType.NOT, "NOT", 700, 10);
		eModel.addLogicElement(LogicType.NOT, "NOT", 700, 600);
		eModel.addLogicElement(LogicType.NAND, "NAND", 1400, 10);
		eModel.addLogicElement(LogicType.NAND, "NAND", 1400, 600);
		eModel.addLogicElement(LogicType.LAMP, "LAMP_SELF", 3000, 10);
		eModel.addLogicElement(LogicType.LAMP, "LAMP_SELF", 3000, 600);

		// Um Verbindungen setzen zu koennen, werden die Inputs und Outputs der
		// einzelnen Bauteile benoetigt. Dazu werden hier die benoetigten Inputs
		// der einzelnen Bauteile gespeichert.
		Map<String, Input> not3Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_3").getInputs());
		Map<String, Input> not4Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_4").getInputs());
		Map<String, Input> nand5Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NAND_5").getInputs());
		Map<String, Input> nand6Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NAND_6").getInputs());
		Map<String, Input> lamp7Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("LAMP_SELF_7").getInputs());
		Map<String, Input> lamp8Inputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("LAMP_SELF_8").getInputs());

		// Hier werden die benoetigten Outputs der einzelnen Bauteile
		// gespeichert
		Map<String, Output> switch1Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("SWITCH_SELF_1").getOutputs());
		Map<String, Output> switch2Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("SWITCH_SELF_2").getOutputs());
		Map<String, Output> not3Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_3").getOutputs());
		Map<String, Output> not4Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_4").getOutputs());
		Map<String, Output> nand5Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NAND_5").getOutputs());
		Map<String, Output> nand6Outputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NAND_6").getOutputs());

		// Der Ausgang von SWITCH_1 wird mit dem Eingang von NOT_3 verbunden.
		eModel.setConnection(switch1Outputs.get(Output.STD_O),
				not3Inputs.get(Input.STD_A));

		// Der Ausgang von NOT_3 wird mit dem ersten Eingang von NAND_5
		// verbunden.
		eModel.setConnection(not3Outputs.get(Output.STD_O),
				nand5Inputs.get(Input.STD_A));

		// Der Ausgang von NAND_6 wird mit dem zweiten Eingang von NAND_5
		// verbunden.
		eModel.setConnection(nand6Outputs.get(Output.STD_O),
				nand5Inputs.get(Input.STD_B));

		// Der Ausgang von NAND_5 wird mit dem ersten Eingang von NAND_6
		// verbunden.
		eModel.setConnection(nand5Outputs.get(Output.STD_O),
				nand6Inputs.get(Input.STD_A));

		// Der Ausgang von NAND_5 wird mit dem Eingang von LAMP_7 verbunden.
		eModel.setConnection(nand5Outputs.get(Output.STD_O),
				lamp7Inputs.get(Input.STD_A));

		// Der Ausgang von SWITCH_2 wird mit dem Eingang von NOT_4 verbunden.
		eModel.setConnection(switch2Outputs.get(Output.STD_O),
				not4Inputs.get(Input.STD_A));

		// Der Ausgang von NOT_4 wird mit dem zweiten Eingang von NAND_6
		// verbunden.
		eModel.setConnection(not4Outputs.get(Output.STD_O),
				nand6Inputs.get(Input.STD_B));

		// Der Ausgang von NAND_6 wird mit dem Eingang von LAMP_8 verbunden.
		eModel.setConnection(nand6Outputs.get(Output.STD_O),
				lamp8Inputs.get(Input.STD_A));

		// Die eben erstellte Schaltung wird als selbstdefiniertes Bauteil
		// gespeichert
		eModel.exportComponent("JUNIT_FLIPFLOP");
	}

	/**
	 * In dieser Methode wird die Schaltung mit folgenden Schalterbelegungen
	 * simuliert: SWITCH_1: L0, SWITCH_2: L0.
	 */
	@Test
	public void simulateFlipFlopReproduction_with_L0_L0() {

		// Setzt den Wert des ersten Schalters auf L0
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_1"))
				.setInputValue(Input.STD_A, LogicValue.L0);

		// Setzt den Wert des zweiten Schalters auf L0
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_2"))
				.setInputValue(Input.STD_A, LogicValue.L0);

		// Der logische Ausgangswert der beiden Schalter wird berechnet und
		// anschliessend werden die nachfolgenden Bauteile berechnet
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_1"))).calcLogicValue();
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_2"))).calcLogicValue();
		sModel.calcElementsToCalc();

		// Speichert den logischen Wert am Ausgang der ersten Lampe
		LogicValue lamp4Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_4"))
				.getLogicValue(Output.STD_O);

		// Speichert den logischen Wert am Ausgang der zweiten Lampe
		LogicValue lamp5Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_5"))
				.getLogicValue(Output.STD_O);

		// Prueft, ob der logische Wert am Ausgang der ersten Lampe UNDEFINED
		// ist
		assertEquals(
				"Der Wert am Ausgang der ersten Lampe ist ungleich UNDEFINED.",
				LogicValue.UNDEFINED, lamp4Value);

		// Prueft, ob der logische Wert am Ausgang der zweiten Lampe UNDEFINED
		// ist
		assertEquals(
				"Der Wert am Ausgang der zweiten Lampe ist ungleich UNDEFINED.",
				LogicValue.UNDEFINED, lamp5Value);

		// Das erstellte Bauteil wird wieder geloescht
		File xmlFile = new File(UtilityConstants.SD_DIRECTORY
				+ "JUNIT_FLIPFLOP" + UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
	}

	/**
	 * In dieser Methode wird die Schaltung mit folgenden Schalterbelegungen
	 * simuliert: SWITCH_1: L0, SWITCH_2: L1.
	 */
	@Test
	public void simulateFlipFlopReproduction_with_L0_L1() {

		// Setzt den Wert des ersten Schalters auf L0
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_1"))
				.setInputValue(Input.STD_A, LogicValue.L0);

		// Setzt den Wert des zweiten Schalters auf L1
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_2"))
				.setInputValue(Input.STD_A, LogicValue.L1);

		// Der logische Ausgangswert der beiden Schalter wird berechnet und
		// anschliessend werden die nachfolgenden Bauteile berechnet
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_1"))).calcLogicValue();
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_2"))).calcLogicValue();
		sModel.calcElementsToCalc();

		// Speichert den logischen Wert am Ausgang der ersten Lampe
		LogicValue lamp4Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_4"))
				.getLogicValue(Output.STD_O);

		// Speichert den logischen Wert am Ausgang der zweiten Lampe
		LogicValue lamp5Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_5"))
				.getLogicValue(Output.STD_O);

		// Prueft, ob der logische Wert am Ausgang der ersten Lampe L0 ist
		assertEquals("Der Wert am Ausgang der ersten Lampe ist ungleich L0.",
				LogicValue.L0, lamp4Value);

		// Prueft, ob der logische Wert am Ausgang der zweiten Lampe L1 ist
		assertEquals("Der Wert am Ausgang der zweiten Lampe ist ungleich L1.",
				LogicValue.L1, lamp5Value);

		// Das erstellte Bauteil wird wieder geloescht
		File xmlFile = new File(UtilityConstants.SD_DIRECTORY
				+ "JUNIT_FLIPFLOP" + UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
	}

	/**
	 * In dieser Methode wird die Schaltung mit folgenden Schalterbelegungen
	 * simuliert: SWITCH_1: L1, SWITCH_2: L0.
	 */
	@Test
	public void simulateFlipFlopReproduction_with_L1_L0() {

		// Setzt den Wert des ersten Schalters auf L1
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_1"))
				.setInputValue(Input.STD_A, LogicValue.L1);

		// Setzt den Wert des zweiten Schalters auf L0
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_2"))
				.setInputValue(Input.STD_A, LogicValue.L0);

		// Der logische Ausgangswert der beiden Schalter wird berechnet und
		// anschliessend werden die nachfolgenden Bauteile berechnet
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_1"))).calcLogicValue();
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_2"))).calcLogicValue();
		sModel.calcElementsToCalc();

		// Speichert den logischen Wert am Ausgang der ersten Lampe
		LogicValue lamp4Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_4"))
				.getLogicValue(Output.STD_O);

		// Speichert den logischen Wert am Ausgang der zweiten Lampe
		LogicValue lamp5Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_5"))
				.getLogicValue(Output.STD_O);

		// Prueft, ob der logische Wert am Ausgang der ersten Lampe L1 ist
		assertEquals("Der Wert am Ausgang der ersten Lampe ist ungleich L1.",
				LogicValue.L1, lamp4Value);

		// Prueft, ob der logische Wert am Ausgang der zweiten Lampe L0 ist
		assertEquals("Der Wert am Ausgang der zweiten Lampe ist ungleich L0.",
				LogicValue.L0, lamp5Value);

		// Das erstellte Bauteil wird wieder geloescht
		File xmlFile = new File(UtilityConstants.SD_DIRECTORY
				+ "JUNIT_FLIPFLOP" + UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
	}

	/**
	 * In dieser Methode wird die Schaltung mit folgenden Schalterbelegungen
	 * simuliert: SWITCH_1: L1, SWITCH_2: L1.
	 */
	@Test
	public void simulateFlipFlopReproduction_with_L1_L1() {

		// Setzt den Wert des ersten Schalters auf L1
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_1"))
				.setInputValue(Input.STD_A, LogicValue.L1);

		// Setzt den Wert des zweiten Schalters auf L1
		sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("SWITCH_2"))
				.setInputValue(Input.STD_A, LogicValue.L1);

		// Der logische Ausgangswert der beiden Schalter wird berechnet und
		// anschliessend werden die nachfolgenden Bauteile berechnet
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_1"))).calcLogicValue();
		((LogicSwitch) sModel.getSimulationElements().get(
				sModel.getLogicElements().get("SWITCH_2"))).calcLogicValue();
		sModel.calcElementsToCalc();

		// Speichert den logischen Wert am Ausgang der ersten Lampe
		LogicValue lamp4Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_4"))
				.getLogicValue(Output.STD_O);

		// Speichert den logischen Wert am Ausgang der zweiten Lampe
		LogicValue lamp5Value = sModel.getSimulationElements()
				.get(sModel.getLogicElements().get("LAMP_5"))
				.getLogicValue(Output.STD_O);

		// Prueft, ob der logische Wert am Ausgang der ersten Lampe L1 ist
		assertEquals("Der Wert am Ausgang der ersten Lampe ist ungleich L1.",
				LogicValue.L1, lamp4Value);

		// Prueft, ob der logische Wert am Ausgang der zweiten Lampe L1 ist
		assertEquals("Der Wert am Ausgang der zweiten Lampe ist ungleich L1.",
				LogicValue.L1, lamp5Value);

		// Das erstellte Bauteil wird wieder geloescht
		File xmlFile = new File(UtilityConstants.SD_DIRECTORY
				+ "JUNIT_FLIPFLOP" + UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
	}
}